| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- import { useFlag, useParams } from 'common'
- import { ExternalLink, RefreshCw, Search, X } from 'lucide-react'
- import { useRouter } from 'next/router'
- import { parseAsString, parseAsStringLiteral, useQueryState } from 'nuqs'
- import React, { useMemo, useRef } from 'react'
- import { Button, Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
- import { Admonition } from 'ui-patterns'
- import { Input } from 'ui-patterns/DataInputs/Input'
- import { PageContainer } from 'ui-patterns/PageContainer'
- import {
- PageHeader,
- PageHeaderAside,
- PageHeaderDescription,
- PageHeaderMeta,
- PageHeaderSummary,
- PageHeaderTitle,
- } from 'ui-patterns/PageHeader'
- import { PageSection, PageSectionContent } from 'ui-patterns/PageSection'
- import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
- import { DeployEdgeFunctionButton } from '@/components/interfaces/EdgeFunctions/DeployEdgeFunctionButton'
- import {
- EDGE_FUNCTIONS_SORT_VALUES,
- EdgeFunctionsSort,
- EdgeFunctionsSortColumn,
- EdgeFunctionsSortDropdown,
- EdgeFunctionsSortOrder,
- } from '@/components/interfaces/EdgeFunctions/EdgeFunctionsSortDropdown'
- import { EdgeFunctionsListItem } from '@/components/interfaces/Functions/EdgeFunctionsListItem'
- import {
- FunctionsEmptyState,
- FunctionsInstructionsLocal,
- } from '@/components/interfaces/Functions/FunctionsEmptyState'
- import { TerminalInstructionsDialog } from '@/components/interfaces/Functions/TerminalInstructionsDialog'
- import { useFunctionsListShortcuts } from '@/components/interfaces/Functions/useFunctionsListShortcuts'
- import DefaultLayout from '@/components/layouts/DefaultLayout'
- import EdgeFunctionsLayout from '@/components/layouts/EdgeFunctionsLayout/EdgeFunctionsLayout'
- import AlertError from '@/components/ui/AlertError'
- import { DocsButton } from '@/components/ui/DocsButton'
- import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
- import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query'
- import { useIsProjectActive } from '@/hooks/misc/useSelectedProject'
- import { DOCS_URL, IS_PLATFORM } from '@/lib/constants'
- import { onSearchInputEscape } from '@/lib/keyboard'
- import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
- import type { NextPageWithLayout } from '@/types'
- const EdgeFunctionsPage: NextPageWithLayout = () => {
- const router = useRouter()
- const { ref } = useParams()
- const showLastHourStats = useFlag('edgeFunctionsRequestMetrics')
- const isProjectActive = useIsProjectActive()
- const searchInputRef = useRef<HTMLInputElement>(null)
- const [search, setSearch] = useQueryState('search', parseAsString.withDefault(''))
- const [sort, setSortQueryParam] = useQueryState(
- 'sort',
- parseAsStringLiteral<EdgeFunctionsSort>(EDGE_FUNCTIONS_SORT_VALUES).withDefault('name:asc')
- )
- const {
- data: functions,
- error,
- isPending: isLoading,
- isError,
- isSuccess,
- isFetching,
- refetch,
- } = useEdgeFunctionsQuery({ projectRef: ref })
- useFunctionsListShortcuts({
- searchInputRef,
- setSearch,
- sort,
- setSort: setSortQueryParam,
- canCreateNew: isProjectActive,
- onCreateNew: () => router.push(`/project/${ref}/functions/new`),
- onRefresh: () => {
- refetch()
- },
- })
- const filteredFunctions = useMemo(() => {
- const temp = (functions ?? []).filter((x) =>
- x.name.toLowerCase().includes(search.toLowerCase())
- )
- const [sortCol, sortOrder] = sort.split(':') as [
- EdgeFunctionsSortColumn,
- EdgeFunctionsSortOrder,
- ]
- const orderMultiplier = sortOrder === 'asc' ? 1 : -1
- return temp.sort((a, b) => {
- if (sortCol === 'name') {
- return a.name.localeCompare(b.name) * orderMultiplier
- }
- if (sortCol === 'created_at') {
- return (a.created_at - b.created_at) * orderMultiplier
- }
- if (sortCol === 'updated_at') {
- return (a.updated_at - b.updated_at) * orderMultiplier
- }
- return 0
- })
- }, [functions, search, sort])
- const hasFunctions = (functions ?? []).length > 0
- return (
- <PageContainer size="large">
- <PageSection>
- <PageSectionContent>
- <div className="flex flex-col gap-6">
- {isLoading && <GenericSkeletonLoader />}
- {isError &&
- (IS_PLATFORM ? (
- <AlertError error={error} subject="Failed to retrieve edge functions" />
- ) : (
- <Admonition type="warning" title="Failed to retrieve edge functions">
- <p className="prose [&>code]:text-xs text-sm">
- Local functions can be found at <code>briven/functions</code> folder.
- </p>
- </Admonition>
- ))}
- {isSuccess && (
- <>
- {hasFunctions ? (
- <div className="space-y-4">
- <div className="flex items-center gap-2">
- <div className="flex items-center gap-2">
- <div className="relative">
- <ShortcutTooltip
- shortcutId={SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH}
- label="Search functions"
- side="bottom"
- >
- <Input
- ref={searchInputRef}
- placeholder="Search function names"
- icon={<Search />}
- size="tiny"
- className="w-32 md:w-64"
- value={search}
- onChange={(event) => setSearch(event.target.value)}
- onKeyDown={onSearchInputEscape(search, setSearch)}
- actions={[
- search && (
- <Button
- key="clear"
- size="tiny"
- type="text"
- icon={<X />}
- onClick={() => setSearch('')}
- className="p-0 h-5 w-5"
- />
- ),
- ]}
- />
- </ShortcutTooltip>
- </div>
- </div>
- <div className="flex items-center gap-2">
- <EdgeFunctionsSortDropdown value={sort} onChange={setSortQueryParam} />
- </div>
- <ShortcutTooltip
- shortcutId={SHORTCUT_IDS.FUNCTIONS_LIST_REFRESH}
- side="bottom"
- >
- <Button
- type="default"
- icon={<RefreshCw />}
- loading={isFetching}
- onClick={() => refetch()}
- >
- Refresh
- </Button>
- </ShortcutTooltip>
- <span className="border-l border-default pl-2 text-xs text-foreground-light">
- {search && filteredFunctions.length !== functions.length
- ? `Viewing ${filteredFunctions.length} of ${functions.length} functions in total`
- : `Viewing ${functions.length} ${functions.length === 1 ? 'function' : 'functions'} in total`}
- </span>
- </div>
- <Card>
- <Table>
- <TableHeader>
- <TableRow>
- <TableHead>Name</TableHead>
- <TableHead>URL</TableHead>
- <TableHead className="hidden 2xl:table-cell">Created</TableHead>
- <TableHead className="lg:table-cell">Updated</TableHead>
- {showLastHourStats && (
- <>
- <TableHead className="lg:table-cell">Total requests (1h)</TableHead>
- <TableHead className="lg:table-cell">5xx error rate (1h)</TableHead>
- </>
- )}
- <TableHead className="hidden 2xl:table-cell">Deployments</TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- <>
- {filteredFunctions.length > 0 ? (
- filteredFunctions.map((item) => (
- <EdgeFunctionsListItem key={item.id} function={item} />
- ))
- ) : (
- <TableRow>
- <TableCell colSpan={showLastHourStats ? 8 : 6}>
- <p className="text-sm text-foreground">No results found</p>
- <p className="text-sm text-foreground-light">
- Your search for "{search}" did not return any results
- </p>
- </TableCell>
- </TableRow>
- )}
- </>
- </TableBody>
- </Table>
- </Card>
- </div>
- ) : (
- <FunctionsEmptyState />
- )}
- </>
- )}
- {!IS_PLATFORM && <FunctionsInstructionsLocal />}
- </div>
- </PageSectionContent>
- </PageSection>
- </PageContainer>
- )
- }
- EdgeFunctionsPage.getLayout = (page: React.ReactElement) => {
- return (
- <DefaultLayout>
- <EdgeFunctionsLayout title="Edge Functions">
- <div className="w-full min-h-full flex flex-col items-stretch">
- <PageHeader size="large">
- <PageHeaderMeta>
- <PageHeaderSummary>
- <PageHeaderTitle>Edge Functions</PageHeaderTitle>
- <PageHeaderDescription>
- Run server-side logic close to your users
- </PageHeaderDescription>
- </PageHeaderSummary>
- <PageHeaderAside>
- <DocsButton href={`${DOCS_URL}/guides/functions`} />
- <Button asChild type="default" icon={<ExternalLink />}>
- <a
- target="_blank"
- rel="noreferrer"
- href="https://github.com/briven/briven/tree/master/examples/edge-functions/briven/functions"
- >
- Examples
- </a>
- </Button>
- {IS_PLATFORM && <DeployEdgeFunctionButton />}
- </PageHeaderAside>
- </PageHeaderMeta>
- </PageHeader>
- {page}
- </div>
- </EdgeFunctionsLayout>
- <TerminalInstructionsDialog />
- </DefaultLayout>
- )
- }
- export default EdgeFunctionsPage
|